from machine import Pin, ADC         #importing Pin and ADC class and timer
import time
from time import sleep		#importing sleep class

# This code runs a Raspbery Pi Pico as an RSSI meter
# It supports TAIT TM8xxx and Kenwood TK-x79 radios
# This project is in support of the TARPN Network
# This is version tadd1.8 dated March 13, 2026

# Version History:
    # 1.0 Initial release
    # 1.1 Development version never released
    # 1.2 Implements measured threshold values TAIT TM-8xxx
    # 1.3 14OCT2025: Removes external ADC voltage reference
    # 1.4 21FEB2025: Implementws peak-hold as coded by Nino Carillo KK4HEJ
    # 1.5 17MAR2025: Implements TK-x90 RSSI curves and multi-radio capabilities
    # 1.6 27SEP2025: Adds a sweep at startup to verify LED function (KA2DEW)
    # 1.7 11MAR2026: Flashes green LEDs when RSSI input overvoltage is detected (KA2DEW)
    # 1.8 13MAR2026: Defeat slow rise for peak indicator, corrected value in TAIT voltage table (KA2DEW)

# Code by Don Rotolo N2IRZ. Released freely for any Amateur Radio use whatsoever. Must be licensed for commercial use.
# Credits to Tadd Torborg, KA2DEW for the more elegant LED section.
# Credits to Nino Carillo for the peak indicator routine.

#####Declarations

LEDF = machine.Pin(0, machine.Pin.OUT)	# This is the green POWER LED, it is always on if code is running.
LED1 = machine.Pin(1, machine.Pin.OUT)	# The number refers to the GPIO number, not the pi pin number
LED2 = machine.Pin(2, machine.Pin.OUT)
LED3 = machine.Pin(3, machine.Pin.OUT)
LED4 = machine.Pin(4, machine.Pin.OUT)
LED5 = machine.Pin(5, machine.Pin.OUT)
LED6 = machine.Pin(6, machine.Pin.OUT)
LED7 = machine.Pin(7, machine.Pin.OUT)
LED8 = machine.Pin(8, machine.Pin.OUT)
LED9 = machine.Pin(9, machine.Pin.OUT)
LED0 = machine.Pin(10, machine.Pin.OUT)
LEDA = machine.Pin(11, machine.Pin.OUT)
LEDB = machine.Pin(12, machine.Pin.OUT)
LEDC = machine.Pin(13, machine.Pin.OUT)
LEDD = machine.Pin(14, machine.Pin.OUT)

# Define GPIOs 16 through 21 as inputs with an internal pull-up resistor
# Supporting radio variants

gpio16 = Pin(16, Pin.IN, Pin.PULL_UP)
gpio17 = Pin(17, Pin.IN, Pin.PULL_UP)
gpio18 = Pin(18, Pin.IN, Pin.PULL_UP)
gpio19 = Pin(19, Pin.IN, Pin.PULL_UP)
gpio20 = Pin(20, Pin.IN, Pin.PULL_UP)
gpio21 = Pin(21, Pin.IN, Pin.PULL_UP)


# RadioTait8xxx = machine.Pin(16, machine.Pin.IN)
# RadioTK790 = machine.Pin(17, machine.Pin.IN)


def SetAllButPowerLedToOff():
   LED1.value(False)
   LED2.value(False)
   LED3.value(False)
   LED4.value(False)
   LED5.value(False)
   LED6.value(False)
   LED7.value(False)
   LED8.value(False)
   LED9.value(False)
   LED0.value(False)
   LEDA.value(False)
   LEDB.value(False)
   LEDC.value(False)
   LEDD.value(False)



### provide test operation for a few
#### Turn all LEDs off to start.
SetAllButPowerLedToOff()


#Turns all the lEDs off to start, except for the Power LED
testDelayLen=0.1
LEDF.value(True)
sleep(testDelayLen)
LED1.value(True)
sleep(testDelayLen)
LED2.value(True)
sleep(testDelayLen)
LED3.value(True)
sleep(testDelayLen)
LED4.value(True)
sleep(testDelayLen)
LED5.value(True)
sleep(testDelayLen)
LED6.value(True)
sleep(testDelayLen)
LED7.value(True)
sleep(testDelayLen)
LED8.value(True)
sleep(testDelayLen)
LED9.value(True)
sleep(testDelayLen)
LED0.value(True)
sleep(testDelayLen)
LEDA.value(True)
sleep(testDelayLen)
LEDB.value(True)
sleep(testDelayLen)
LEDC.value(True)
sleep(testDelayLen)
LEDD.value(True)
sleep(testDelayLen)

### Now put them back off
LEDF.value(False)
sleep(testDelayLen)
LED1.value(False)
sleep(testDelayLen)
LED2.value(False)
sleep(testDelayLen)
LED3.value(False)
sleep(testDelayLen)
LED4.value(False)
sleep(testDelayLen)
LED5.value(False)
sleep(testDelayLen)
LED6.value(False)
sleep(testDelayLen)
LED7.value(False)
sleep(testDelayLen)
LED8.value(False)
sleep(testDelayLen)
LED9.value(False)
sleep(testDelayLen)
LED0.value(False)
sleep(testDelayLen)
LEDA.value(False)
sleep(testDelayLen)
LEDB.value(False)
sleep(testDelayLen)
LEDC.value(False)
sleep(testDelayLen)
LEDD.value(False)
sleep(testDelayLen)

LEDF.value(True)	#Indicates power, and code running. Never touched again after this.
RSSI = ADC(28)          #creating RSSI object
ADC_offset = ADC(27)	#creating ADC Offset object

# Read the pin states, using board markings
value_a = gpio16.value()
value_b = gpio17.value()
value_c = gpio18.value()
value_d = gpio19.value()
value_e = gpio20.value()
value_f = gpio21.value()




### This section handles the different radio RSSI curves.

### For TAIT TM8xxx Radios, jumper on Pin 21 ("A")
if value_a == False:
    d120dBm = 0.4     ##   RED
    d117dBm = 0.7     ##   RED
    d114dBm = 0.759   ##   RED
    d111dBm = 0.822   ##   YELLOW
    d108dBm = 0.890   ##   YELLOW
    d105dBm = 0.959   ##   YELLOW
    d102dBm = 1.030   ##   YELLOW
    d99dBm = 1.103    ##   GREEN
    d96dBm = 1.179    ##   GREEN
    d93dBm = 1.255    ##   GREEN
    d90dBm = 1.331    ##   GREEN
    d87dBm = 1.406    ##   GREEN
    d84dBm = 1.481    ##   GREEN
    d81dBm = 1.519    ##   GREEN
    ###overflow = 2   ## test
    overflow = 3.1
    maxSmeter=d81dBm+0.07

### For TK790 Radios, jumper on Pin 22 ("B")
elif value_b == False:
    d120dBm = .88115
    d117dBm = .958
    d114dBm = 1.034
    d111dBm = 1.11
    d108dBm = 1.186
    d105dBm = 1.251
    d102dBm = 1.315
    d99dBm = 1.379
    d96dBm = 1.455
    d93dBm = 1.531
    d90dBm = 1.609
    d87dBm = 1.675
    d84dBm = 1.736
    d81dBm = 1.796
    overflow = 3.1
    maxSmeter=d81dBm+0.07


# # For error condition (No pins, undefined (or not-yet-defined) pins, or more than one pin).
# # Displays a pattern on LEDs forever
else:
    while True:
        LEDF.value(True)
        LED1.value(False)
        sleep(.2)
        LEDF.value(False)
        LED1.value(True)
        sleep(.2)




#This section manages smoothing out reading values
readings = [1, 1]
reading = 1
max_samples = 3
def mean(nums):
    return float(sum(nums)) / max(len(nums),1)

# Peak-Hold feature, added by Nino on 12 Feb 2025
# This section is for tracking the maximum RSSI reading value over the last <history_length> samples
sample_rate = 10 			# Hz, this will be used to generate the sleep() argument at the end of the loop below.
sample_time = 1/sample_rate
peak_hold_time = 2 			# seconds to hold the peak LED illuminated
history_length = peak_hold_time * sample_rate # Number of samples needed in the circular buffer
peak_sample_buffer = [] 	# Create a sample buffer.
# The .append() method is slow, so do this now instead of in the loop below
for i in range(history_length):
    peak_sample_buffer.append(0)
# Create an index to track where the next sample will be stored in the buffer.
# This will also be the location of the oldest sample once the buffer is filled.
peak_sample_index = 0

def debugBlinking():
 testDelayLen = 0.1
 LED1.value(False)
 sleep(testDelayLen)
 LED1.value(True)
 sleep(testDelayLen)
 LED1.value(False)
 sleep(testDelayLen)
 LED1.value(True)
 sleep(testDelayLen)
 LED1.value(False)
 sleep(testDelayLen)
 LED1.value(True)
 sleep(testDelayLen)
 LED1.value(False)
 sleep(testDelayLen)

##### PAUSE AND DISPLAY ERROR FOR OVERFLOW
### This is a loop that will do its own display action if the AtoD input
### shows an overflow condition in the instantaneous compensated RSSI reading.
### The value set for 'overflow' is such that the Tait should never be able to receive so high a value.
### This is a trap for a hardware failure or assembly error. 

### We Stay in this function until the overflow ends.
### This function takes no action on the display until we've been overflowed for a full second.
### The display showing the RSSI reading and the peak hold will stay lit but will freeze until that full second is up.
### After that, the display blanks and the green LEDs on the right will all flash as one, 5 times a second.
### Once the AtoD readings are within rational limits, the overflow condition ends, and we exit this routine
### resuming the normal RSSI reading and peak hold operations.  
def PauseAndDisplayErrorForOverflow():
 global compensated_RSSI_value, overflow
 overflowCountThreshold_l=10
 overflowCounter_l=0     ## increment each time we get a reading of overflow.  When we get to some high count, start flashing the lights
 while compensated_RSSI_value > overflow:	# > -80dBm (or less)
   overflowCounter_l=overflowCounter_l+1;            ## we got an overflow reading.  Hold off on S meter display until we count 10 readings
   blinkdelay_l=0.1;
   #### We don't take over the display until the overflow condition has remained for a full second.  
   if overflowCounter_l > overflowCountThreshold_l:
     overflowCounter_l = 11;    #set to Max        ## we have > 10 readings -- flash the green LEDs until the overflow goes away.
     SetAllButPowerLedToOff()
     LED8.value(True)
     LED9.value(True)
     LED0.value(True)
     LEDA.value(True)
     LEDB.value(True)
     LEDC.value(True)
     LEDD.value(True)
     sleep(blinkdelay_l)
     LED8.value(False)
     LED9.value(False)
     LED0.value(False)
     LEDA.value(False)
     LEDB.value(False)
     LEDC.value(False)
     LEDD.value(False)
   sleep(blinkdelay_l)
   getMeanRssi()          ### Get new reading for compensated_RSSI_value

##### GET MEAN RSSI
#  This function reads the AtoD converter input, performs an offset calculation to account for higher than 0 voltages
#  that the AtoD would be putting out for 'no reading', then services two averaging buffers, one for peak-hold reading
#  and one for the immediate RSSI smoothing.   The smoothing buffer always is set to immediate RSSI if the RSSI reading rises,
#  and then the smoothing decays slowly when the RSSI value is flowing.
#  This function returns the immediate compensated RSSI value, as well as a smoothed RSSI value, and a peak value.
#  If the compensated RSSI input ever exceeds the maximum displayed value, the value is truncated to the maxSmeter value before
#  the value is saved into the averaging arrays.
#
##### uses
### ADC hardware inputs
### readings                This is the buffer used to average/smooth the RSSI value as it decays
### peak_sample_buffer      This is the buffer used to manage the Peak Reading
### peak_sample_index       Index into the peak reading sample buffer
##### sets
### peak_value              used to set the slowly decaying right LED when the RSSI has decreased  max of the peak sample buffer
### compensated_RSSI_value  this is the instantaneous RSSI after compensating for the base voltage on the A2D readings
### RSSI_value              this is the mean of the last bunch of RSSI readings.
def getMeanRssi():
   global compensated_RSSI_value, RSSI_value, peak_value, peak_sample_index, maxSmeter, readings, peak_sample_buffer, peak_sample_index
#These two reads of the ADCs assume the internal 3.3 volt reference.
   raw_RSSI_value = RSSI.read_u16() * 3.3 / 65536  		#reading analog pin 34, ADC2
   raw_ADC_offset = ADC_offset.read_u16() * 3.3 / 65536	#Reading another ADC's ground value to find ADC offset, pin 32 ADC 1

   compensated_RSSI_value = raw_RSSI_value - raw_ADC_offset
   storeThisValue_l=compensated_RSSI_value         ## storeThisValue is used in the peak-hold, max and mean calculations
   if storeThisValue_l > maxSmeter:
      storeThisValue_l = maxSmeter;                ## limit the highest storable and reportable reading to the top of the LED display

   readings.append(storeThisValue_l)

   # Added by Nino on 12 Feb 2025
   # Track the adc readings in a buffer. This duplicates some functionality of the line above,
   # but we need more samples to have a longer peak persistence time.
   peak_sample_buffer[peak_sample_index] = storeThisValue_l
   # Increment peak_sample_index and constrain maximum
   peak_sample_index += 1
   if peak_sample_index >= history_length:           #Wrap the peak reading buffer if needed
      peak_sample_index = 0                          #it was needed.  Wrap back to index of 0
   # Calculate the peak value in the last <history_length> samples
   peak_value = max(peak_sample_buffer)
   # end added
   RSSI_value = mean(readings)
   if RSSI_value < compensated_RSSI_value:     ### Reading went up.  Flush the averaging buffer with the new higher reading
      if len(readings) >= max_samples:
         readings.pop(0)
      readings.append(storeThisValue_l)
      if len(readings) >= max_samples:
         readings.pop(0)
      RSSI_value = mean(readings)
       
###debugBlinking()

#Main loop, runs forever.
while True:
 getMeanRssi()    ### Read the A2D, do averaging and peak reading   ## sets peak_value, compensated_RSSI_value, RSSI_value
 ### peak_value              used to set the slowly decaying right LED when the RSSI has decreased  max of the peak sample buffer
 ### compensated_RSSI_value  this is the instantaneous RSSI after compensating for the base voltage on the A2D readings
 ### RSSI_value              this is the mean of the last bunch of RSSI readings.
 ### Check for an overflow condition.  This is a really high voltage that shouldn't be possible.
 PauseAndDisplayErrorForOverflow()      ## Hang forever if the AtoD input is higher than the Tait should provide. 


 #Turns all the LEDs off (=False) each cycle through the While loop
 SetAllButPowerLedToOff()

 #Turns on LEDs according to RSSI voltage.
 #Thank you to KA2DEW for elegantification of this section. He is a programmer, I (N2IRZ) am a hack.
 if RSSI_value > d120dBm:	# =-120 dB (or less)
     LED1.value(True)
 if RSSI_value > d117dBm:	# =-117 dBm indication
     LED2.value(True)
 if RSSI_value > d114dBm:	# =-114 dBm indication
     LED3.value(True)
 if RSSI_value > d111dBm:	# =-111 dBm indication
     LED4.value(True)
 if RSSI_value > d108dBm:	# =-108 dBm indication
     LED5.value(True)
 if RSSI_value > d105dBm:	# =-105 dBm indication
     LED6.value(True)
 if RSSI_value > d102dBm:	# =-102 dBm indication
     LED7.value(True)
 if RSSI_value > d99dBm:	# =-99 dBm indication
     LED8.value(True)
 if RSSI_value > d96dBm:	# =-96 dBm indication
     LED9.value(True)
 if RSSI_value > d93dBm:	# =-93 dBm indication
     LED0.value(True)
 if RSSI_value > d90dBm:	# =-90 dBm indication
     LEDA.value(True)
 if RSSI_value > d87dBm:	# =-87 dBm indication
     LEDB.value(True)
 if RSSI_value > d84dBm:	# =-84 dBm indication
     LEDC.value(True)
 if RSSI_value > d81dBm:	# =-81 dBm (or more)
     LEDD.value(True)

# Used during development to see readings and values on the console
# print (RSSI_value)

 if len(readings) >= max_samples:	#Limits number of samples in buffer
     readings.pop(0)

 # Added by Nino on 12 Feb 2025
 # Illuminate the LED corresponding to the calculated peak value.
 # Only one LED will be selected, so this is structured with 'elif' statements
 # Start with the LED corresponding to the highest value.
 if peak_value > d81dBm:		#-81 dBm
    LEDD.value(True)
 elif peak_value > d84dBm:		#-84 dBm
    LEDC.value(True)
 elif peak_value > d87dBm:		#-87 dBm
    LEDB.value(True)
 elif peak_value > d90dBm:		#-90 dBm
    LEDA.value(True)
 elif peak_value > d93dBm:		#-93 dBm
    LED0.value(True)
 elif peak_value > d96dBm:		#-96 dBm
    LED9.value(True)
 elif peak_value > d99dBm:		#-99 dBm
    LED8.value(True)
 elif peak_value > d102dBm:		#-102 dBm
    LED7.value(True)
 elif peak_value > d105dBm:		#-105 dBm
    LED6.value(True)
 elif peak_value > d108dBm:		#-108 dBm
    LED5.value(True)
 elif peak_value > d111dBm:		#-111 dBm
    LED4.value(True)
 elif peak_value > d114dBm:		#-114 dBm
    LED3.value(True)
 elif peak_value > d117dBm:		#-117 dBm
    LED2.value(True)
 elif peak_value > d120dBm:		#-120 dBm
    LED1.value(True)

## Used during development to verify some readings
#  print (RSSI_value)
#  print (value_f, value_e, value_d, value_c, value_b, value_a)
#  sleep(0.01)

 # Sleep a little time, dynamic based on sample_rate variable declared above
 sleep(sample_time)

####################################


